/**
 * 
 */
package fluo.centralserver;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;

import fluo.androidclient.FluoMessage;
import fluo.desktopserver.DesktopServer;

/**
 * @author maherbeg
 *
 * Created: Apr 2, 2009
 * Modified: Apr 2, 2009
 */
public class CentralServer implements CentralServerDetails, Runnable {

	ServerSocket RegisterSocket;
	private HashMap<String, RegisterDesktop> servers;
	private HashMap<String, ClientInformation> clients; //key=user name, value = client details
	private boolean debug = true;
	
	public CentralServer(){
		servers = new HashMap<String, RegisterDesktop>();
		clients = new HashMap<String, ClientInformation>();
		try {
			RegisterSocket = new ServerSocket();
			RegisterSocket.setReuseAddress(true);
			RegisterSocket.bind(new InetSocketAddress(CentralServerDetails.ip, CentralServerDetails.port));
		} catch (IOException e) {
			System.out.println("CentralServer.CentralServer(): Error binding address and creating socket...exiting system.");
			e.printStackTrace();
			System.exit(0);
		}
		
		Connection conn = null;
		Statement stat = null;

		try {
			if(debug) System.out.println("CentralServer.CentralServer(): Reading client list from db");
			Class.forName("org.sqlite.JDBC");
			conn = DriverManager.getConnection("jdbc:sqlite:fluo.db");
			
			//Now let's retrieve all the clients from our database
			stat = conn.createStatement();
			stat.executeUpdate("CREATE TABLE IF NOT EXISTS users (username, password);");
			ResultSet rs = stat.executeQuery("SELECT * FROM users;"); 
			while(rs.next()) 
				clients.put(rs.getString("username"), new ClientInformation(rs.getString("username"), rs.getString("password")));
			if(debug) System.out.println("CentralServer.CentralServer(): Client list: " + clients);
			rs.close();
			
			//Now let's retrieve all the permissions available
			stat.executeUpdate("CREATE TABLE IF NOT EXISTS desktops (servers, username)");
			rs = stat.executeQuery("SELECT * FROM desktops");
			while(rs.next()){
				ClientInformation cur = clients.get(rs.getString("username"));
				if(cur != null){
					cur.addDesktop(rs.getString("servers"));
				}
			}
			
			rs.close();
			
			if(stat != null)stat.close();
			if(conn != null) conn.close();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		Thread t = new Thread(this);
		t.start();
	}

	public void run() {
		Socket register = null;
		while(true){
			try {
				if(debug) System.out.println("CentralServer.run(): Waiting for a new connection...");
				if(register == null || register.isClosed()){
					register = RegisterSocket.accept();
					System.out.println("Socket reopening...");
				}
				System.out.println("CentralServer.run(): Connection made");
				ObjectOutputStream oos = new ObjectOutputStream(register.getOutputStream());
				ObjectInputStream ois = new ObjectInputStream(register.getInputStream());
				
				Object receiveData = null;
				try {
					receiveData = ois.readObject();
				} catch (ClassNotFoundException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				FluoMessage rec = (FluoMessage)receiveData;
				if(rec != null){
					System.out.println("CentralServer.run(): Client is "+rec.getSingleString());
					if(receiveData instanceof FluoMessage && rec.getSingleString().equalsIgnoreCase("android")){
						if(debug) System.out.println("CentralServer.run(): Android client found");
						receiveData = ois.readObject();
						if(receiveData instanceof FluoMessage){
							FluoMessage ret = (FluoMessage)receiveData;
							if(!ret.getRegister()){ //Are we logging in or registering?
								String unpw[] = ret.getUNPW().split(":");
								if(unpw != null && unpw.length == 2){
									String recuser = unpw[0]; String recpass = unpw[1];
									if(clients.containsKey(recuser) && clients.get(recuser).getPass().equals(recpass)){
										if(debug) System.out.println("CentralServer.run(): Valid user, with permissions to: " + clients.get(recuser).getDesktopServers());
										if(debug) System.out.println("CentralServer.run(): Available servers: " + servers);
										oos.writeObject(new FluoMessage("YES"));

										ArrayList<String> desktopips = new ArrayList<String>();
										for(String name : clients.get(recuser).getDesktopServers()){
											if(servers.containsKey(name)){
												RegisterDesktop rd = servers.get(name);
												desktopips.add( rd.getIp() + ":" + rd.getPort());
											}
										}

										ret = new FluoMessage(desktopips);
										oos.writeObject(ret);
										oos.flush();
									}else{ //invalid user name or password
										oos.writeObject(new FluoMessage("NO"));
									}
								} else{ //no user name/password found
									oos.writeObject(new FluoMessage("NO"));
								}
							} else{ //register the client
								if(registerUser(ret))
									oos.writeObject(new FluoMessage("YES"));
								else
									oos.writeObject(new FluoMessage("NO"));
							}
						}

					}else{
						if(debug) System.out.println("CentralServer.run(): New connection created, spawning new registration thread.");
						try {
							receiveData = ois.readObject();
						} catch (ClassNotFoundException e) {
							System.out.println("Unknown object");
							e.printStackTrace();
						}
						if(receiveData instanceof RegisterDesktop){ //Lets register a desktop into our system
							RegisterDesktop desktop = (RegisterDesktop)receiveData;

							if(registerDesktop(desktop))
								desktop.setAccepted(true);
							else
								desktop.setAccepted(false);

							//Let the server know that we have accepted or denied them
							oos.writeObject(desktop);
						} else if(receiveData instanceof ClientInformation){ //received a username we want to input
							ClientInformation recci = (ClientInformation)receiveData;
							RegisterDesktop desktop = (RegisterDesktop)ois.readObject();
							if(clients.containsKey(recci.getUser())){
								registerUserDesktop(desktop, recci);
							}
						}
					}

				}
				register.close();
			} catch (IOException e) {
				System.out.println("CentralServer.run(): Error accepting new connection, please try connecting again.");
				e.printStackTrace();
				try {
					register.close();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			} catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				try {
					register.close();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			}
		}

	}

	private boolean registerUserDesktop(RegisterDesktop desktop, ClientInformation recci) {
		Connection conn = null;
		Statement stat = null;
		PreparedStatement prep = null;
		try {
			if(debug) System.out.println("CentralServer.registerUserDesktop(): Registering a new user...");
			Class.forName("org.sqlite.JDBC");
			conn = DriverManager.getConnection("jdbc:sqlite:fluo.db");
			stat = conn.createStatement();
			stat.executeUpdate("CREATE TABLE IF NOT EXISTS desktops (servers,username)");
			stat.close();
			prep = conn.prepareStatement("INSERT INTO desktops values (?,?)");

			prep.setString(1, desktop.getServerName());
			prep.setString(2, recci.getUser());
			prep.execute();
			clients.get(recci.getUser()).addDesktop(desktop.getServerName());
			if(debug) System.out.println("CentralServer.registerUserDesktop(): Successful user permission insertion");				
		
			
			if(prep!=null)prep.close();
			if(stat != null)stat.close();
			if(conn != null) conn.close();
			
			return true;
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
		return false;
	}

	private boolean registerUser(FluoMessage ret) {
		Connection conn = null;
		Statement stat = null;
		PreparedStatement prep = null;
		try {
			System.out.println("CentralServer.registerUser(): Registering a new user...");
			Class.forName("org.sqlite.JDBC");
			conn = DriverManager.getConnection("jdbc:sqlite:fluo.db");
			stat = conn.createStatement();
			stat.executeUpdate("CREATE TABLE IF NOT EXISTS users (username, password)");
			stat.close();
			prep = conn.prepareStatement("INSERT INTO users values (?,?)");
			String unpw[] = ret.getUNPW().split(":");
			if(unpw != null && unpw.length == 2){
				prep.setString(1, unpw[0]);
				prep.setString(2, unpw[1]);
				prep.execute();
				clients.put(unpw[0], new ClientInformation(unpw[0], unpw[1]));
				System.out.println("CentralServer.registerUser(): Successful user insertion");				
			}
			
			
			if(prep!=null)prep.close();
			if(stat != null)stat.close();
			if(conn != null) conn.close();
			
			return true;
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return false;
	}

	//Returns true if we could add the desktop to our list of clients
	//Returns false if the name already exists
	public boolean registerDesktop(RegisterDesktop desktop) {
		if(debug) System.out.println("CentralServer.registerDesktop(): Registering new desktop " + desktop);
		if(servers.containsKey(desktop.getServerName())){
			return false;
		}
		
		servers.put(desktop.getServerName(), desktop);
		return true;
	}
	
	public static void main(String args[]){
		CentralServer cs = new CentralServer();
		DesktopServer ds = new DesktopServer();
	}
}
